#include <54func.h>
typedef struct Conn_s{
    int isAlive;
    int netfd;
    time_t lastActive;
}Conn_t;
int main(int argc,char *argv[])
{
    // ./0_azhen 192.168.187.128 1234
    ARGS_CHECK(argc,3);
    //创建socket套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    //初始化
    struct sockaddr_in serveraddr;
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
    //允许该套接字的端口在关闭后立即被重新使用。
    int reuse = 1;
    //SOL_SOCKET：表示在套接字级别设置选项
    //SO_REUSEADDR：是一q个常用的套接字选项，用于允许端口重用
    int ret = setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
    ERROR_CHECK(ret,-1,"setsockopt");
    //bind：将socket与网络ip以及端口进行绑定
    ret = bind(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr));
    ERROR_CHECK(ret,-1,"bind");
    listen(sockfd,50);


//实现一个聊天室：即一个人发消息，其他人都能收到，这里服务器起到一个转发消息的作用
    //1、创建epoll实例
    int epfd = epoll_create(1);

    //2、设置监听集合
    struct epoll_event events;
    events.events = EPOLLIN;
    events.data.fd = sockfd;
    epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&events);
    
    Conn_t conn[1024];
    for(int i=0;i<1024;i++){
        conn[i].isAlive = 0;//表示未有连接接入
    }
    int fdtoidx[1024];//主要实现fdtoidx[fd] --> idx 可以通过文件描述符去获取其再conn数组的下标
    for(int i=0;i<1024;i++)
    {
        fdtoidx[i] = -1;
    }
    
    
    //用于记录新客户端的插入位置，确保新客户端能被正确地插入到客户端数组
    int curidx = 0;
    char buf[4096]={0};
    time_t now;
    //3、等待epoll_wait
    while(1)
    {
        struct epoll_event readySet[1024];//获取就绪集合的数组
        int readyNum = epoll_wait(epfd,readySet,1024,1000);//-1表示无限循环下去，1000表示每1秒钟进行一次超时
        
        now = time(NULL);
        printf("now： %s\n",ctime(&now));//每1秒钟检查一次，是否有就绪
       
        //4、遍历就绪集合
        for(int i=0;i<readyNum;i++){
            //判断是否有新连接接入
            if(readySet[i].data.fd == sockfd){
                int netfd = accept(sockfd,NULL,NULL);
               //将每次新增的连接都加入到定义的con数据结构中，用于记录
               conn[curidx].isAlive = 1;
               conn[curidx].netfd = netfd;
               conn[curidx].lastActive = time(NULL);
               fdtoidx[netfd] = curidx;//加入到哈希索引中
                printf("id = %d,netfd = %d,lastActive = %s\n",curidx,netfd,ctime(&conn[curidx].lastActive));
            
               //每加入一个新连接，都要将新连接加入监听集合中
                events.events = EPOLLIN;
                events.data.fd = netfd;
                epoll_ctl(epfd,EPOLL_CTL_ADD,netfd,&events);
                ++curidx;
            }else{
                //否者就是连接已经存在，并且连接要发消息给其它所有的客户端
                int netfd = readySet[i].data.fd;
                bzero(buf,sizeof(buf));
                ssize_t sret = recv(netfd,buf,sizeof(buf),0);

                //短线重连
                if(sret == 0){
                    printf("1 client is over!\n");
                    epoll_ctl(epfd,EPOLL_CTL_DEL,netfd,&events);
                    int idx =fdtoidx[netfd];//通过文件描述符获取其再数组的下标位置
                    conn[idx].isAlive = 0;
                    fdtoidx[netfd] = -1;
                    close(netfd);
                    continue;
                }
                
                //当客户端发完消息后，要更新其lastActive时间
                int idx = fdtoidx[netfd];//只有netfd能用
                conn[idx].lastActive = time(NULL);
                //之所以用i，这里i跟curidx是一样的
                printf("id = %d,netfd = %d,lastActive = %s\n",i,netfd,ctime(&conn[idx].lastActive));
                
                //将读取到的消息发送给其它所有的客户端
                for(int j=0;j<curidx;j++){
                    if(conn[j].isAlive == 1 && conn[j].netfd !=netfd ){
                        send(conn[j].netfd,buf,strlen(buf),0);
                    }
                }
            }
        }
        //处理完所有的就绪事件，超时踢人
        for(int i = 0;i<curidx;i++){
            if(conn[i].isAlive == 1 && now - conn[i].lastActive > 10){
                epoll_ctl(epfd,EPOLL_CTL_DEL,conn[i].netfd,NULL);
                close(conn[i].netfd);
                conn[i].isAlive = 0;
                fdtoidx[conn[i].netfd] = -1;
            }
        }


    }

    
    return 0;
}
